/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package org.ruslan.kolobok;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.ruslan.kolobok.model.Barrel;
import org.ruslan.kolobok.model.Fx;
import org.ruslan.kolobok.model.LevelObject.RenderType;
import org.ruslan.kolobok.model.Soldier;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GLCommon;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g3d.decals.CameraGroupStrategy;
import com.badlogic.gdx.graphics.g3d.decals.Decal;
import com.badlogic.gdx.graphics.g3d.decals.DecalBatch;
import com.badlogic.gdx.graphics.g3d.decals.DecalMaterial;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;

public class WorldRenderer {
	private static final float FRUSTUM_WIDTH = 1500; // 15m
	private static final float FRUSTUM_HEIGHT = 1000; // 10m
	private World world;
	public Camera cam;
	private DecalBatch decalBatch;
	// private TextureRegion background;
	private ShapeRenderer shapeRenderer;
	private GLCommon gl;

	public WorldRenderer(World world) {
		this.world = world;
		// this.cam = new OrthographicCamera(FRUSTUM_WIDTH, FRUSTUM_HEIGHT);
		// set initial camera
		cam = new PerspectiveCamera(67, FRUSTUM_WIDTH, FRUSTUM_HEIGHT);
		cam.position.x = 0;
		cam.position.y = 200;
		cam.position.z = 400;
		cam.lookAt(cam.position.x, 0, -1000000000);
		cam.near = 1;
		cam.far = 100000;

		shapeRenderer = new ShapeRenderer();
		decalBatch = new DecalBatch();

		// Comparator<Decal> comparator = new Comparator<Decal>() {
		// @Override
		// public int compare (Decal o1, Decal o2) {
		// float dist1 = cam.position.dst(o1.getPosition());
		// float dist2 = cam.position.dst(o2.getPosition());
		// return (int)Math.signum(dist2 - dist1);
		// }
		// };
		decalBatch.setGroupStrategy(new CameraGroupStrategy(cam));
		// decalBatch.setGroupStrategy(new CameraGroupStrategy(cam,
		// comparator));
		// decalBatch.setGroupStrategy(new DefaultGroupStrategy());

		// this.gl = gl;

		// gl.glClearColor(0.5f, 0.5f, 1f, 1);

		// gl = Gdx.gl10;
		//
		// gl.glDepthFunc(GL10.GL_LESS);
		// // gl.glEnable(GL10.GL_DEPTH_TEST);
		// // gl.glDisable(GL10.GL_CULL_FACE);
		
		soldiersIterator = world.soldiers.iterator();
	}
	
	Iterator<Soldier> soldiersIterator;

	Decal[] decals = new Decal[100];
	private int nextDecal;

	Decal[] horizontalDecals = new Decal[100];
	private int nextHorDecal;
	
	Decal[] mirroredDecals = new Decal[100];
	private int nextMirroredDecal;

	private Decal nextDecal(TextureRegion textureRegion, boolean transparent, boolean mirror) {
		return nextDecal(textureRegion.getRegionWidth(), textureRegion.getRegionHeight(), textureRegion, transparent,
				false, mirror);
	}

	private Decal nextDecal(float width, float height, TextureRegion textureRegion, boolean hasTransparency,
			boolean horizontal, boolean mirror) {
		Decal decal = horizontal ? horizontalDecals[nextHorDecal] : mirror ? mirroredDecals[nextMirroredDecal] : decals[nextDecal];
		if (decal == null) {
			decal = Decal.newDecal(width, height, textureRegion, hasTransparency);
			if (horizontal) {
				decal.rotateX(-90);
				horizontalDecals[nextHorDecal] = decal;
			} else if (mirror) {
				decal.rotateY(180);
				mirroredDecals[nextMirroredDecal] = decal;
			} 
			else {
				decals[nextDecal] = decal;
			}
		} else {
			// decal.setTextureRegion(textureRegion);
			// decal.setDimensions(width, height);
			// // decal.setWidth(width);
			// // decal.setHeight(height);

			int srcBlendFactor = hasTransparency ? GL10.GL_SRC_ALPHA : DecalMaterial.NO_BLEND;
			int dstBlendFactor = hasTransparency ? GL10.GL_ONE_MINUS_SRC_ALPHA : DecalMaterial.NO_BLEND;
			decal.setTextureRegion(textureRegion);
			decal.setBlending(srcBlendFactor, dstBlendFactor);
			decal.setDimensions(width, height);
			decal.setColor(1, 1, 1, 1);
		}
		if (horizontal) {
			nextHorDecal++;
		}
		else if (mirror) {
			nextMirroredDecal++;
		}
		else {
			nextDecal++;
		}

		return decal;
	}

	public void render() {
		for (int i = 0; i < 4; i++) {
			nextDecal = 0;
			nextHorDecal = 0;
			nextMirroredDecal = 0;
			cam.position.x = 0;
			cam.lookAt(0, 0, -1000000000);
			cam.update();
			renderBackground();
			cam.position.x = world.playerX;
			cam.lookAt(cam.position.x, 0, -1000000000);
			cam.update();
			// cam.apply(gl);

			// renderLevel(0);
			// renderLevel(world.width);

			float shift1 = ((int) (world.playerX - world.width / 2) / (int) world.width) * world.width;
			float shift2 = shift1 + world.width;
			// System.out.println("shift1=" + shift1 + ";world.playerX=" +
			// world.playerX);
			Decal decal = nextDecal(world.width, 10000, Assets.dirt, true, true, false);
			decal.getPosition().set(shift1 + world.width / 2, 0, 0);
			// decal.rotateX(-90);
			decalBatch.add(decal);

			decal = nextDecal(world.width, 10000, Assets.dirt, true, true, false);
			decal.getPosition().set(shift2 + world.width / 2, 0, 0);
			// decal.rotateX(-90);
			decalBatch.add(decal);

			decalBatch.flush();

			renderObjects(decalBatch, shift1);
			renderObjects(decalBatch, shift2);
			// decalBatch.flush();

			renderActors(decalBatch);
			decalBatch.flush();

			renderEffects(decalBatch, 0);
			decalBatch.flush();

			shapeRenderer.setProjectionMatrix(cam.combined);
			shapeRenderer.begin(ShapeType.Line);
			shapeRenderer.setColor(0, 0, 1, 1);
			float x = world.width + shift1;
			shapeRenderer.line(x, 0, 0, x, 0, -10000);
			x = world.width + shift2;
			shapeRenderer.line(x, 0, 0, x, 0, -10000);

			shapeRenderer.end();
		}

		// drawAux();
	}

	private void drawAux() {
		shapeRenderer.setProjectionMatrix(cam.combined);
		GraphicUtils.drawGrid(shapeRenderer, 10000, 200, GraphicUtils.XZ);
		GraphicUtils.drawAxis(shapeRenderer, 10000);

		shapeRenderer.begin(ShapeType.Box);
		shapeRenderer.setColor(1, 0, 1, 0.5f);
		synchronized (World.class) {
			for (Soldier soldier : world.soldiers) {
				// shapeRenderer.rect(soldier.bounds.x, soldier.bounds.y,
				// soldier.bounds.width, soldier.bounds.height);
				if (soldier.isActive()) {
					shapeRenderer.box(soldier.getPosition().x - soldier.getWidth() / 2, soldier.getPosition().y,
							soldier.getPosition().z, soldier.getWidth(), soldier.getHeight(), 0);
				}
			}
		}
		shapeRenderer.end();

		shapeRenderer.begin(ShapeType.Line);
		shapeRenderer.setColor(1, 0, 0, 1);
		float x = world.playerX - World.VANISHING_LINE;
		shapeRenderer.line(x, 0, 0, x, 0, -10000);
		x = world.playerX + World.VANISHING_LINE * 2;
		shapeRenderer.line(x, 0, 0, x, 0, -10000);

		x = world.playerX - 1000;
		shapeRenderer.line(x, 0, -1000, x, 1000, -1000);
		x = world.playerX + 1000;
		shapeRenderer.line(x, 0, -1000, x, 1000, -1000);

		shapeRenderer.end();
	}

	private void renderBackground() {

		Decal decal = nextDecal(51200, 19800, Assets.hills, true, false, false);
		decal.getPosition().set(5000, 9000, -20000);
		decalBatch.add(decal);
		decalBatch.flush();
	}

	Map<String, TextureAtlas> atlases = new HashMap<String, TextureAtlas>();
	// Map<String, Decal> decals = new HashMap<String, Decal>();
	Map<String, TextureRegion> textures = new HashMap<String, TextureRegion>();

	private Decal getDecal(Soldier soldier) {

		Decal decal;
		if (soldier.getRenderType() == RenderType.ATLAS) {
			String graphics = soldier.getGraphics();
			TextureAtlas atlas = atlases.get(graphics);
			if (atlas == null) {
				atlas = new TextureAtlas(Gdx.files.internal(graphics));
				atlases.put(graphics, atlas);
			}
	
			String textureName = selectName(soldier);
			TextureRegion textureRegion = textures.get(textureName);
			if (textureRegion == null) {
				textureRegion = atlas.findRegion(textureName);
				textures.put(textureName, textureRegion);
			}
			

			decal = nextDecal(textureRegion, true, soldier.getVelocity().x < 0);
		}
		else {
			TextureRegion textureRegion = Assets.textureRegionsMap.get(soldier.getGraphics());
//			if (textureRegion == null) {
//				textureRegion = 
//				Assets.textureRegionsMap.put(soldier.getGraphics(), textureRegion);
//			}
			decal = nextDecal(textureRegion, true, false);
		}

		return decal;
	}

	private Decal getDecal(Barrel barrel) {

		Decal decal = nextDecal(Assets.barrel, true, false);

		return decal;
	}

	private Decal getDecal(Fx fx) {

		String graphics = fx.getGraphics();
		Decal decal = nextDecal(Assets.textureRegionsMap.get(graphics), true, false);

		return decal;
	}

	private String selectName(Soldier soldier) {
		String name = null;
		switch (soldier.getState()) {
		case DIE:
			name = "die";
			break;

		case SHOOT:
			name = "shoot";
			break;
		case WALK:
			if (soldier.getStateTime() % 1 > 0.5) {
				name = "walk1";
			} else {
				name = "walk2";
			}
			break;

		default:
			break;
		}

		return name;
	}

	private void renderActors(DecalBatch decalBatch) {
		// Vector3 camPosition = cam.position.cpy();
		// Vector3 camUp = cam.up.cpy().nor();
		synchronized (World.class) {
			for (Soldier soldier : world.soldiers) {
				if (soldier.isActive() && soldier.getPosition().x < world.playerX + World.VANISHING_LINE) {
					// batch.draw(Assets.soldier, soldier.bounds.x,
					Decal decal = getDecal(soldier);
					decal.getPosition().set(soldier.getPosition().x, soldier.getPosition().y + soldier.getHeight() / 2,
							soldier.getPosition().z);
					decal.setDimensions(soldier.getWidth(), soldier.getHeight());
					// decal.lookAt(camPosition, camUp);
					// decal.setBlending(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
					decalBatch.add(decal);
				}
			}
		}
	}
	
	private void renderObjects(DecalBatch decalBatch, float shift) {
		synchronized (World.class) {

			for (Barrel barrel : world.barrels) {
				if (barrel.isActive() && barrel.getPosition().x < world.playerX + World.VANISHING_LINE
						&& barrel.getPosition().x > world.playerX - World.VANISHING_LINE) {
					Decal decal = getDecal(barrel);
					decal.getPosition().set(barrel.getPosition().x + shift,
							barrel.getPosition().y + barrel.getHeight() / 2, barrel.getPosition().z);
					decal.setDimensions(barrel.getWidth(), barrel.getHeight());
					decalBatch.add(decal);
				}
			}
		}
	}

	private void renderEffects(DecalBatch decalBatch, float shift) {
		synchronized (World.class) {

			// System.out.println(world.effects.size());
			for (Fx fx : world.effects) {
				// System.out.println(fx);
				// if (barrel.isActive()) {
				// batch.draw(Assets.soldier, soldier.bounds.x,
				Decal decal = getDecal(fx);
				decal.getPosition().set(fx.getPosition().x + shift, fx.getPosition().y, fx.getPosition().z);
				if (fx.getType().getName().equals("bullet_splash")) {
					decal.getPosition().y += fx.getHeight() / 2;
				}

				decal.setDimensions(fx.getWidth(), fx.getHeight());
				decalBatch.add(decal);
				// }
			}
		}
	}

}
